package org.project.d.starter.network;

import lombok.extern.slf4j.Slf4j;
import org.project.d.network.Dispatch;
import org.project.d.network.DispatchManager;
import org.project.d.network.msg.Message;
import org.project.d.network.msg.Transmission;
import org.project.d.network.msg.serializer.DeSerialized;
import org.project.d.starter.network.annotation.HandlerMapping;
import org.project.d.starter.network.annotation.MsgHandler;
import org.springframework.aop.support.AopUtils;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author lin
 * @version V1.0
 * @description
 * @date 2020/8/5 3:04 下午
 */
@Slf4j
public class DispatchRegistrar implements ApplicationListener<ApplicationReadyEvent> {

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        registerAnnotation(event);
        registerDispath(event);
    }

    private void registerDispath(ApplicationReadyEvent event) {
        Map<String, Dispatch> dispatchMap = event.getApplicationContext().getBeansOfType(Dispatch.class);
        dispatchMap.forEach((k, v) -> DispatchManager.addDispatch(v));
        Map<String, DispatchManager.RegisterDispatch> registerDispatchMap = event.getApplicationContext().getBeansOfType(DispatchManager.RegisterDispatch.class);
        registerDispatchMap.forEach((k, v) -> DispatchManager.addDispatch(v));
    }

    private void registerAnnotation(ApplicationReadyEvent event) {
        Map<String, Object> msgHandlers = event.getApplicationContext().getBeansWithAnnotation(MsgHandler.class);
        msgHandlers.forEach((k, v) -> {
            Class<?> handlerType = v.getClass();
            final Class<?> userType = ClassUtils.getUserClass(handlerType);
            Map<Method, Dispatch> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<Dispatch>) method -> getMappingForMethod(method, userType, v));
            if (log.isDebugEnabled()) {
                log.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
            }
            methods.forEach((key, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
                registerHandlerMethod(handlerType, invocableMethod, mapping);
            });
        });
    }

    private void registerHandlerMethod(Class<?> handlerType, Method invocableMethod, Dispatch mapping) {
        DispatchManager.addDispatch(mapping);
    }

    private Dispatch getMappingForMethod(Method method, Class<?> userType, Object instance) {
        Dispatch info = null;
        try {
            info = createRequestMappingInfo(method, instance);
        } catch (IllegalAccessException | InstantiationException e) {
            log.error("创建Dispatch失败", e);
        }
        return info;
    }

    @Nullable
    private Dispatch createRequestMappingInfo(AnnotatedElement element, Object instance) throws IllegalAccessException, InstantiationException {
        HandlerMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, HandlerMapping.class);
        if (requestMapping != null) {
            Dispatch.DispatchBuilder dispatchBuilder = Dispatch.builder().commandCode(requestMapping::value);
            Class<? extends DeSerialized> deSerializaClass = requestMapping.deSerializa();
            if (!deSerializaClass.isInterface()) {
                dispatchBuilder.deSerialized(deSerializaClass.newInstance());
            }
            if (element instanceof Method) {
                Method method = (Method) element;
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    oneArgs(instance, dispatchBuilder, method, parameterTypes[0]);
                } else if (parameterTypes.length==2){
                    multiArgs(instance, dispatchBuilder, method, parameterTypes);
                }else if (parameterTypes.length==0){
                    oneArgs(instance,dispatchBuilder,method,null);
                }
            }
            return dispatchBuilder.build();
        }
        return null;
    }

    private void oneArgs(Object instance, Dispatch.DispatchBuilder dispatchBuilder, Method method, Class<?> parameterType) {
        if (parameterType==null){
            dispatchBuilder.msgHandler(message -> {
                return getTransmission(instance, method, null);
            });
        } else if (ClassUtils.isAssignable(parameterType, Message.class)) {
            dispatchBuilder.msgHandler(message -> {
                return getTransmission(instance, method, message);
            });
        }else {
            dispatchBuilder.msgHandler(message -> {
                return getTransmission(instance, method, message.getBodyObj().get());
            });
            dispatchBuilder.msgClass(parameterType);
        }
    }

    private Transmission getTransmission(Object instance, Method method, Object message) {
        try {
            if (message!=null){
                return (Transmission) method.invoke(instance, message);
            }else {
                return (Transmission) method.invoke(instance);
            }
        } catch (IllegalAccessException | InvocationTargetException e) {
            log.error("调用错误", e);
        } catch (ClassCastException e) {
            log.error("返回参数请使用 org.project.d.network.msg.Transmission 或者 void");
        }
        return null;
    }

    private void multiArgs(Object instance, Dispatch.DispatchBuilder dispatchBuilder, Method method, Class<?>[] parameterTypes) {
        if (parameterTypes.length > 2) {
            throw new RuntimeException("标记 @HandlerMapping 注解的方法必须要只能有2个需要接受的对象参数");
        }
        List<Class<?>> notHaveMessge = Arrays.stream(parameterTypes).filter(parameterType -> !ClassUtils.isAssignable(parameterType, Message.class)).collect(Collectors.toList());
        if (notHaveMessge.size() == 2) {
            throw new RuntimeException("标记 @HandlerMapping 注解的方法必须要只能有2个需要接受的对象参数并且其中之一为只能为 Message");
        }
        dispatchBuilder.msgClass(notHaveMessge.get(0));
        dispatchBuilder.msgHandler(message -> {
            Object[] args = new Object[2];
            if (ClassUtils.isAssignable(parameterTypes[0], Message.class)) {
                args[0] = message;
                args[1] = message.getBodyObj().get();
            } else {
                args[0] = message.getBodyObj().get();
                args[1] = message;
            }
            try {
                return (Transmission) method.invoke(instance, args);
            } catch (IllegalAccessException | InvocationTargetException e) {
                log.error("调用错误", e);
            } catch (ClassCastException e) {
                log.error("返回参数请使用 org.project.d.network.msg.Transmission 或者 void");
            }
            return null;
        });
    }
}
